/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ts,  NodePredicate } from 'qvog-dsl';
import { native, HomeCheckHint } from 'qvog-dsl';

const SEVERITY = 2;
const DOC_PATH = '';
const DESCRIPTION = 'Enforce the use of `as const` over literal type assertions.';


export const Target: NodePredicate<ts.Node> = {
    is(node: ts.Node): node is ts.Node {
        return ts.isAsExpression(node) ||
            ts.isTypeAssertionExpression(node) ||
            ts.isVariableDeclaration(node) ||
            ts.isPropertyDeclaration(node);
    },
};

export default native<HomeCheckHint>()()
    .match(Target)
    .when((node: ts.Node): boolean => {
        if (!getFileExtension(node.getSourceFile().fileName, 'ts')) {
            return false;
        }
        if (ts.isAsExpression(node) || ts.isTypeAssertionExpression(node)) {
            if (shouldReportAssertion(node)) {
                return true;
            }
        }
        if (ts.isVariableDeclaration(node) || ts.isPropertyDeclaration(node)) {
            if (shouldReportVariableDeclaration(node)) {
                return true;
            }
        }
        return false;
    })
    .report({
        severity: SEVERITY,
        description: DESCRIPTION,
        docPath: DOC_PATH,
    });

function getFileExtension(filePath: string, filetype: string): boolean {
    const match = filePath.match(/\.([0-9a-zA-Z]+)$/);
    if (match) {
        const extension = match[1];
        return extension === filetype;
    }
    return false;
};

function shouldReportAssertion(node: ts.AssertionExpression): boolean {
    const { expression, type } = node;
    // 检查对象字面量中的属性断言
    if (ts.isObjectLiteralExpression(expression)) {
        return expression.properties.some(prop => {
            if (ts.isPropertyAssignment(prop)) {
                const value = prop.initializer;
                return ts.isAsExpression(value) && shouldReportAssertion(value);
            }
            return false;
        });
    }
    // 处理基本类型断言
    if (type.kind === ts.SyntaxKind.StringKeyword ||
        type.kind === ts.SyntaxKind.NumberKeyword) {
        return false;
    }
    // 不需要报错的情况
    if (!ts.isLiteralTypeNode(type)) { return false };
    if (ts.isTypeReferenceNode(type)) { return false };
    // 处理模板字面量
    if (ts.isTemplateLiteral(expression)) {
        return false;
    }
    // 检查字面量类型断言
    if (ts.isLiteralTypeNode(type)) {
        if (ts.isStringLiteral(expression) && ts.isStringLiteral(type.literal)) {
            return expression.text === type.literal.text;
        }
        if (ts.isNumericLiteral(expression) && ts.isNumericLiteral(type.literal)) {
            return expression.text === type.literal.text;
        }
    }
    return false;
};

function shouldReportVariableDeclaration(node: ts.VariableDeclaration | ts.PropertyDeclaration): boolean {
    const { type, initializer } = node;
    if (!type || !initializer) { return false };
    // 跳过基本类型注解
    if (!ts.isLiteralTypeNode(type) &&
        (type.kind === ts.SyntaxKind.StringKeyword ||
            type.kind === ts.SyntaxKind.NumberKeyword)) {
        return false;
    }
    // 跳过函数类型
    if (ts.isFunctionTypeNode(type) || ts.isMethodDeclaration(node)) {
        return false;
    }
    // 跳过模板字面量
    if (ts.isTemplateLiteral(initializer)) {
        return false;
    }
    // 检查字面量类型注解
    if (ts.isLiteralTypeNode(type)) {
        if (ts.isStringLiteral(initializer) && ts.isStringLiteral(type.literal)) {
            return initializer.text === type.literal.text;
        }
        if (ts.isNumericLiteral(initializer) && ts.isNumericLiteral(type.literal)) {
            return initializer.text === type.literal.text;
        }
    }
    return false;
};
